// PanasonicSimulator.cs
//
// Implements HomeUX.Components.PanasonicSimulator.
//
// See ..\..\Drivers\Panasonic\DocFiles\PanasonicDriver.htm for simulator usage information.
//

using System;
using System.Globalization;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using HomeUX.Utilities;

namespace HomeUX.Components
{

/// <summary>
/// Simulates a Panasonic D816 Digital Super Hybrid System telephone PBX.
/// </summary>
///
/// <remarks>
/// See <n>PanasonicDriver</n> documentation for <r>PanasonicSimulator</r> usage information.
/// </remarks>
///
public class PanasonicSimulator : VirtualSerialPort
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
	// Private State
	//

	/// <summary>
	/// This object is locked before a thread accesses this object, to serialize access to the
	/// object.
	/// </summary>
	object _lock = new object();

	/// <summary>
	/// Serial port read buffer, i.e. data to send to client.
	/// </summary>
	StringBuilder _sendToClient = new StringBuilder(1000);

	/// <summary>
	/// Work queue for scheduling asynchronous operations.
	/// </summary>
	WorkQueue _workQueue;

    /// <summary>
    /// Identifies a test being performed using this simulator, or 0 if no test is being
    /// performed.
    /// </summary>
    int _test;

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Public Methods
	//

	/// <summary>
	/// Creates an instance of this class.
	/// </summary>
	///
	public PanasonicSimulator() : this(new string[0])
	{
	}

    /// <summary>
    /// Creates an instance of this class.  Allows simulator arguments to be specified.
    /// </summary>
    ///
    /// <param name="args">Simulator arguments.  See PanasonicDriver.htm for details.</param>
    ///
    public PanasonicSimulator(string[] args)
    {
        // parse <args>
        foreach (string arg in args)
        {
            try
            {
                string[] argParts = arg.Split('=');
                if (argParts.Length != 2)
                    throw new ArgumentException();
                string argName = argParts[0];
                string argValue = argParts[1];
                switch (argName)
                {
                case "Test":
                    _test = System.Xml.XmlConvert.ToInt32(argValue);
                    break;
                default:
                    throw new ArgumentException();
                }
            }
            catch (Exception ex)
            {
                if ((ex is FormatException) || (ex is OverflowException) ||
                    (ex is ArgumentException))
                {
                    throw new VirtualSerialPortException("Invalid argument: {0}", arg);
                }
                else
                    throw;
            }
        }
    }

	///////////////////////////////////////////////////////////////////////////////////////////////
	// IDisposable Overrides
	//

    /// <summary>
    /// Implements <n>IDisposable.Dispose</n>.
    /// </summary>
    ///
    public override void Dispose()
    {
		lock (_lock)
		{
			base.Dispose();
			if (_workQueue != null)
			{
				_workQueue.Dispose();
				_workQueue = null;
			}
		}
    }

	///////////////////////////////////////////////////////////////////////////////////////////////
	// VirtualSerialPort Overrides
	//

    /// <summary>
    /// Overrides <r>VirtualSerialPort.SupportsLineBasedTextMode</r>.
    /// </summary>
    ///
	public override bool SupportsLineBasedTextMode
	{
		// this virtual serial port only supports line-based text mode
		get
		{
			return true;
		}
	}

    /// <summary>
    /// Overrides <r>VirtualSerialPort.NewLine</r>.
    /// </summary>
    ///
    public override string NewLine
	{
		get
		{
            return "\r\n";
		}
        set
        {
            if (value != "\r\n")
            {
                throw new InvalidOperationException(
					"PanasonicSimulator expects lines it receives to be terminated by CRLF");
			}
        }
	}

    /// <summary>
    /// Overrides <r>VirtualSerialPort.Open</r>.
    /// </summary>
    ///
    public override void Open()
    {
        lock (_lock)
        {
			// initialize state
			InitializeState();

			// create a work queue for queueing events
			_workQueue = new WorkQueue("PanasonicSimulator");

            // write initial strings
            WriteToClient("\x00\xF8\x13\x11");
            _workQueue.Add(10000, delegate
            {
                WriteToClient("ATQ0E0V1S0=1X0&D0" + NewLine);
            });

			// debugging feedback
			Trace("Initialized");

            // simulate an incoming phone call if simulator argument Test=1 was specified
            if (_test == 1)
            {
                DateTime now = DateTime.Now;
                string notification = String.Format(
                    "  Date     Time    Ext CO        Dial Number        Ring Duration  Acc code  CD" + NewLine +
                    "--------------------------------------------------------------------------------" + NewLine +
                    "{0:MM/dd/yy} {0:hh}:{0:mm}{0:tt}   105 01 <I>2065551234  JOHNS II,G 0'13 00:00'15" + NewLine,
                    now);
                _workQueue.Add(20000, delegate
                {
                    WriteToClient(notification + NewLine);
                });
            }
		}
    }

    /// <summary>
    /// Overrides <r>VirtualSerialPort.ReadExisting</r>.
    /// </summary>
    ///
    public override string ReadExisting()
	{
		lock (_lock)
		{
			string text = _sendToClient.ToString();
			_sendToClient.Length = 0;
			return text;
		}
	}

    /// <summary>
    /// Overrides <r>VirtualSerialPort.WriteLine</r>.
    /// </summary>
    /// 
    /// <param name="text">See <r>VirtualSerialPort.WriteLine</r>.</param>
    ///
    public override void WriteLine(string text)
	{
		// this hardware ignores text written to it
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Private Properties & Methods
	//

	/// <summary>
	/// Initializes (or reinitializes) the state of the simulated hardware.
	/// </summary>
	///
	void InitializeState()
	{
		lock (_lock)
		{
			// TODO...
		}
	}

	/// <summary>
	/// Formats a string and sends it string to the client asynchronously.  The client will receive
	/// the string on the virtual serial port.
	/// </summary>
	/// 
	/// <param name="format">The error message format string.  If <pr>args</pr> is empty,
	/// 	<pr>format</pr> is used without formatting.</param>
	///
	/// <param name="args">Formatting arguments.</param>
	///
	void WriteToClient(string format, params object[] args)
	{
		lock (_lock)
		{
			WriteToClient((args.Length == 0) ? format : String.Format(format, args));
		}
	}

	/// <summary>
	/// Sends a string to the client asynchronously.  The client will receive the string on the
	/// virtual serial port.
	/// </summary>
	///
	void WriteToClient(string text)
	{
		lock (_lock)
			_sendToClient.Append(text);
		_workQueue.Add(delegate
		{
            FireDataReceived(SerialData.Chars);
		});
	}

	/// <summary>
	/// Writes a formatted string to the debug output window.
	/// </summary>
	///
	static void Trace(string format, params object[] args)
	{
		// static -- no lock required
		Util.Trace("PanasonicSimulator: {0}", Util.Format(format, args));
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Nested Types
	//

	/// <summary>
	/// Indicates an invalid Panasonic command.
	/// </summary>
	///
	class PanasonicInvalidCommandException : Exception
	{
	}
}

}
